package com.chapter.sample.redis.service;

import cn.hutool.core.util.IdUtil;
import com.chapter.sample.redis.mylock.DistributedLockFactory;
import com.chapter.sample.redis.mylock.RedisDistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Service
public class InventoryService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DistributedLockFactory distributedLockFactory;

    @Value("${server.port}")
    private String port;

    @Autowired
    private RedissonClient redissonClient;

    //9.0版本 自动续期
    //7.0版本，如何将lock/unlock + lua脚本自研版本的redis分布式锁！
    public String saleRedisson() {
        String msg = "";
        RLock redissonLock = redissonClient.getLock("zLock");
        redissonLock.lock();
        try {
            String result = redisTemplate.opsForValue().get("inventory001");
            //2、
            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
            if (inventoryNum > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
                msg = "成功卖出一个商品，库存余量：" + inventoryNum;
                log.info("{},服务端口号:{}", inventoryNum, port);
                try {
                    TimeUnit.SECONDS.sleep(120);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //testReEntry();

            } else {
                msg = "商品卖完了 ！";
            }
        } finally {
            //自己持有，才删除（非特别高的情况下，不怎么写也性）
            if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {
                redissonLock.unlock();
            }
        }
        return msg + "服务端口号:{" + port + "}";
    }

    //8.0版本 自动续期
    //7.0版本，如何将lock/unlock + lua脚本自研版本的redis分布式锁！
    public String sale() {
        String msg = "";
        Lock redisLock = distributedLockFactory.getDistributedLock("REDIS");
        redisLock.lock();
        try {
            String result = redisTemplate.opsForValue().get("inventory001");
            //2、
            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
            if (inventoryNum > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
                msg = "成功卖出一个商品，库存余量：" + inventoryNum;
                log.info("{},服务端口号:{}", inventoryNum, port);
                try {
                    TimeUnit.SECONDS.sleep(120);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //testReEntry();

            } else {
                msg = "商品卖完了 ！";
            }
        } finally {
            redisLock.unlock();
        }
        return msg + "服务端口号:{" + port + "}";
    }

    private void testReEntry() {
        Lock redisLock = distributedLockFactory.getDistributedLock("REDIS");
        redisLock.lock();
        try {
            log.info("测试可重入锁");
        } finally {
            redisLock.unlock();
        }
    }

    /*//7.0版本，如何将lock/unlock + lua脚本自研版本的redis分布式锁！
    public String sale() {
        String msg = "";
        Lock redisLock = distributedLockFactory.getDistributedLock("REDIS");
        redisLock.lock();
        try {
            String result = redisTemplate.opsForValue().get("inventory001");
            //2、
            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
            if (inventoryNum > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
                msg = "成功卖出一个商品，库存余量：" + inventoryNum;
                log.info("{},服务端口号:{}", inventoryNum, port);
                testReEntry();
            } else {
                msg = "商品卖完了 ！";
            }
        } finally {
            redisLock.unlock();
        }
        return msg + "服务端口号:{" + port + "}";
    }

    private void testReEntry() {
        Lock redisLock = distributedLockFactory.getDistributedLock("REDIS");
        redisLock.lock();
        try {
            log.info("测试可重入锁");
        } finally {
            redisLock.unlock();
        }
    }*/

    /*
    //6.0 不满足可重入性
    //6.0 删除原子 lua脚本
    //v5.0 存在问题：存在的问题；删除操作不是一行原子操作命令，需要用lua脚步进行操作
    //改进点，只能删除属于自己的key，不能删除别人的
    //v5.0 判断加锁与解锁是不是同一个客户端,同一个才行，自己只能删除自己的锁，不误删它人的
    //if (redisTemplate.opsForValue().get(key).equals(uuidVal)){
    //    redisTemplate.delete(key);
    //}
    //v4.0 存在问题：redisTemplate.delete(key); 自能自己删除自己的锁，不可以删除别人的，要判断
    //加锁、过期时间、保证原子性
    //多线程判断，防止虚假唤醒，用while替代递归
    //用自旋替代递归重试
    public String sale() {
        String msg = "";
        String key = "xxxxx";
        String uuidVal = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        //加锁、过期时间保证原子性
        while (!redisTemplate.opsForValue().setIfAbsent(key, uuidVal, 10L, TimeUnit.SECONDS)) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {

            }
        }
        //redisTemplate.expire(key, 30, TimeUnit.SECONDS);

        try {
            String result = redisTemplate.opsForValue().get("inventory001");
            //2、
            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
            if (inventoryNum > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
                msg = "成功卖出一个商品，库存余量：" + inventoryNum;
                log.info("{},服务端口号:{}", inventoryNum, port);
            } else {
                msg = "商品卖完了 ！";
            }
        } finally {
            String redisScript = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
                    "return redis.call('del',KEYS[1]) " +
                    "else " +
                    "return 0" +
                    "end";
            redisTemplate.execute(new DefaultRedisScript<>(redisScript, Boolean.class), Arrays.asList(key), uuidVal);
        }
        return msg + "服务端口号:{" + port + "}";
    }*/


//    //v5.0 存在问题：存在的问题；删除操作不是一行原子操作命令，需要用lua脚步进行操作
//    //改进点，只能删除属于自己的key，不能删除别人的
//    //v5.0 判断加锁与解锁是不是同一个客户端,同一个才行，自己只能删除自己的锁，不误删它人的
//    //if (redisTemplate.opsForValue().get(key).equals(uuidVal)){
//    //    redisTemplate.delete(key);
//    //}
//    //v4.0 存在问题：redisTemplate.delete(key); 自能自己删除自己的锁，不可以删除别人的，要判断
//    //加锁、过期时间、保证原子性
//    //多线程判断，防止虚假唤醒，用while替代递归
//    //用自旋替代递归重试
//    public String sale() {
//        String msg = "";
//        String key = "xxxxx";
//        String uuidVal = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
//        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, uuidVal);
//        //加锁、过期时间保证原子性
//        while (!redisTemplate.opsForValue().setIfAbsent(key, uuidVal, 10L, TimeUnit.SECONDS)) {
//            try {
//                TimeUnit.SECONDS.sleep(1);
//            } catch (Exception e) {
//
//            }
//        }
//        //redisTemplate.expire(key, 30, TimeUnit.SECONDS);
//
//        try {
//            String result = redisTemplate.opsForValue().get("inventory001");
//            //2、
//            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
//            if (inventoryNum > 0) {
//                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
//                msg = "成功卖出一个商品，库存余量：" + inventoryNum;
//                log.info("{},服务端口号:{}", inventoryNum, port);
//            } else {
//                msg = "商品卖完了 ！";
//            }
//        } finally {
//            //改进点，只能删除属于自己的key，不能删除别人的
//            //v5.0 判断加锁与解锁是不是同一个客户端,同一个才行，自己只能删除自己的锁，不误删它人的
//            if (redisTemplate.opsForValue().get(key).equals(uuidVal)){
//                redisTemplate.delete(key);
//            }
//        }
//        return msg + "服务端口号:{" + port + "}";
//    }

    /*
    //v4.0 存在问题：redisTemplate.delete(key); 自能自己删除自己的锁，不可以删除别人的，要判断
    //加锁、过期时间、保证原子性
    //多线程判断，防止虚假唤醒，用while替代递归
    //用自旋替代递归重试
    public String sale() {
        String msg = "";
        String key = "xxxxx";
        String uuidVal = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, uuidVal);
        //加锁、过期时间保证原子性
        while (!redisTemplate.opsForValue().setIfAbsent(key, uuidVal, 10L, TimeUnit.SECONDS)) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {

            }
        }
        //redisTemplate.expire(key, 30, TimeUnit.SECONDS);

        try {
            String result = redisTemplate.opsForValue().get("inventory001");
            //2、
            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
            if (inventoryNum > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
                msg = "成功卖出一个商品，库存余量：" + inventoryNum;
                log.info("{},服务端口号:{}", inventoryNum, port);
            } else {
                msg = "商品卖完了 ！";
            }
        } finally {
            redisTemplate.delete(key);
        }
        return msg + "服务端口号:{" + port + "}";
    }*/

    /*//多线程判断，防止虚假唤醒，用while替代递归
    //用自旋替代递归重试
    public String sale() {
        String msg = "";
        String key = "xxxxx";
        String uuidVal = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, uuidVal);
        //不用递归了，高并发下容易出错,使用自旋代替递归重试;也不用if了，用while来替代
        while (!redisTemplate.opsForValue().setIfAbsent(key, uuidVal)){
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {

            }
        }

        try {
            String result = redisTemplate.opsForValue().get("inventory001");
            //2、
            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
            if (inventoryNum > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
                msg = "成功卖出一个商品，库存余量：" + inventoryNum;
                log.info("{},服务端口号:{}", inventoryNum, port);
            } else {
                msg = "商品卖完了 ！";
            }
        } finally {
            redisTemplate.delete(key);
        }
        return msg + "服务端口号:{" + port + "}";
    }*/
    /*
    递归重试，容易导致stackoveroflowererror，所以不推荐，另外高并发唤醒后推荐使用while判断而不是if
    public String sale() {
        String msg = "";
        String key = "xxxxx";
        String uuidVal = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, uuidVal);
        if (!flag) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {

            }
            sale();
        } else {
            try {
                String result = redisTemplate.opsForValue().get("inventory001");
                //2、
                Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
                if (inventoryNum > 0) {
                    redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
                    msg = "成功卖出一个商品，库存余量：" + inventoryNum;
                    log.info("{},服务端口号:{}", inventoryNum, port);
                } else {
                    msg = "商品卖完了 ！";
                }
            } finally {
                redisTemplate.delete(key);
            }
        }
        return msg + "服务端口号:{" + port + "}";
    }*/

    /*public String sale() {
        String msg = "";

        lock.lock();
        try {
            String result = redisTemplate.opsForValue().get("inventory001");
            //2、
            Integer inventoryNum = result == null ? 0 : Integer.parseInt(result);
            if (inventoryNum > 0) {
                redisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNum));
                msg = "成功卖出一个商品，库存余量：" + inventoryNum;
                log.info("{},服务端口号:{}", inventoryNum, port);
            } else {
                msg = "商品卖完了 ！";
            }
        } finally {
            lock.unlock();
        }
        return msg + "服务端口号:{" + port + "}";
    }*/
}
